/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.uml_generator.manager.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

import com.genia.toolbox.basics.bean.DisplayObject;
import com.genia.toolbox.basics.bean.impl.ConstantDelimiter;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.uml_generator.Constants;
import com.genia.toolbox.uml_generator.bean.ModelGeneratorContext;
import com.genia.toolbox.uml_generator.manager.DelegateManager;
import com.genia.toolbox.uml_generator.model.AbstractDelegatableObject;
import com.genia.toolbox.uml_generator.model.AttributeModel;
import com.genia.toolbox.uml_generator.model.OperationModel;
import com.genia.toolbox.uml_generator.model.impl.AttributeModelImpl;

/**
 * implementation of {@link DelegateManager}.
 */
public class DelegateManagerImpl
    extends AbstractManager
    implements DelegateManager
{

  /**
   * generate the Delegate class for the model.
   * 
   * @param context
   *          the generation context
   * @param delegatable
   *          the delegatable to generate the class for
   * @throws TechnicalIOException
   *           when an I/O error occurred
   * @see com.genia.toolbox.uml_generator.manager.DelegateManager#generateDelegate(com.genia.toolbox.uml_generator.bean.ModelGeneratorContext,
   *      com.genia.toolbox.uml_generator.model.AbstractDelegatableObject)
   */
  public void generateDelegate(ModelGeneratorContext context, AbstractDelegatableObject delegatable)
      throws TechnicalIOException
  {
    try {
      if (!delegatable.getOperationModel().isEmpty()) {
        // The delegate object.
        AbstractDelegatableObject delegatableInterface = delegatable.getUndecoraredObject();
        AbstractDelegatableObject delegateObject = getModelDecorator().getDelegateObject(delegatableInterface);
        // The parent of the delegate object.
        AbstractDelegatableObject delegateObjectParent = delegateObject.getParent() == null ? null : getModelDecorator().getDelegateObject(delegateObject.getParent().getUndecoraredObject());
        // Check that the parent has methods, otherwise consider that there is
        // no parent.
        if (delegateObjectParent != null && delegateObjectParent.getOperationModel().isEmpty()) {
          delegateObjectParent = null;
        }
        File delegateObjectClassFile = new File(context.getDirectoryForPackage(context.getConfiguration().getSrcDir(), delegateObject), delegateObject.getName() + ".java");
        delegateObjectClassFile.getParentFile().mkdirs();
        if (delegateObjectClassFile.exists()) {
          // if a delegate class already exists, quit.
          return;
        }
        PrintWriter out = new PrintWriter(new FileOutputStream(delegateObjectClassFile));
        context.getGeneratedSources().add(delegateObjectClassFile);
        getPrinterManager().printPackage(out, delegateObject).println();
        getPrinterManager().printComment(out, "delegate for the class {@link " + delegatableInterface.getQualifiedName() + "}.");
        out.append("public class ").append(delegateObject.getName());

        if (delegateObjectParent != null) {
          out.append(" ").append(Constants.EXTENDS_RESERVED_WORD).append(" ").append(delegateObjectParent.getQualifiedName());
        }

        out.append("{");

        getPrinterManager().printComment(out, "the object that will use this delegate.");
        out.append("private final ").append(delegatableInterface.getQualifiedName()).append(" ").append(getNameManager().getLowerCaseFirstLetterName(delegatableInterface)).append(";");

        getPrinterManager().printComment(out, "constructor.", "@param " + getNameManager().getLowerCaseFirstLetterName(delegatableInterface) + " the object that will use this delegate.");
        out.append("public ").append(delegateObject.getName()).append("(").append(delegatableInterface.getQualifiedName()).append(" ").append(getNameManager().getLowerCaseFirstLetterName(delegatableInterface)).append(
            ")\n{");
        if (delegateObjectParent != null) {
          out.append("super(").append(getNameManager().getLowerCaseFirstLetterName(delegatableInterface)).append(");");
        }
        out.append("this.").append(getNameManager().getLowerCaseFirstLetterName(delegatableInterface)).append(" = ").append(getNameManager().getLowerCaseFirstLetterName(delegatableInterface)).append(";\n");
        out.append("}\n");

        for (OperationModel operation : delegateObject.getOperationModel()) {
          if (delegateObjectParent == null || !delegateObjectParent.getOperationModel().contains(operation)) {
            getPrinterManager().printMethod(out, operation);
          }
        }

        out.append("}");

        out.close();

      }
    }
    catch (IOException e) {
      throw getExceptionManager().convertException(e);
    }
  }



  /**
   * this method prints the declaration of the delegate and the call to the
   * delegate methods for operations.
   * 
   * @param <T>
   *          the type of the {@link Appendable}
   * @param out
   *          the {@link Appendable} to print to
   * @param delegatable
   *          the object that has method to delegate
   * @return the {@link Appendable} that has been print to
   * @throws TechnicalIOException
   *           when an I/O error occurred
   * @see com.genia.toolbox.uml_generator.manager.DelegateManager#printDelegateCall(java.lang.Appendable,
   *      com.genia.toolbox.uml_generator.model.AbstractDelegatableObject)
   */
  public <T extends Appendable> T printDelegateCall(T out, AbstractDelegatableObject delegatable)
      throws TechnicalIOException
  {
    try {
      if (!delegatable.getOperationModel().isEmpty()) {
        AbstractDelegatableObject delegateObject = getModelDecorator().getDelegateObject(delegatable.getUndecoraredObject());

        getPrinterManager().printAttribute(out, getDelegateAttribute(delegateObject));

        for (OperationModel operation : delegatable.getOperationModel()) {
          if (delegatable.getParent() != null && delegatable.getParent().getOperationModel().contains(operation)) {
            operation.setOverride(true);
          }
          getPrinterManager().printSignature(out, operation).append("{");
          if (!operation.hasVoidReturnType()) {
            out.append("return ");
          }
          out.append(getNameManager().getLowerCaseFirstLetterName(delegateObject)).append(".").append(operation.getName());
          getCollectionManager().join(out, operation.getParameterModels(), new ConstantDelimiter("(", ",", ");"), new DisplayObject<AttributeModel>()
          {
            public void append(Appendable appendable, AttributeModel object)
                throws IOException
            {
              appendable.append(object.getName());
            }
          });
          out.append("}");
          operation.setOverride(false);
        }
      }
    }
    catch (IOException e) {
      throw getExceptionManager().convertException(e);
    }
    return out;
  }



  /**
   * returns an {@link AttributeModel} representing the field containing the
   * delegate.
   * 
   * @param delegateObject
   *          the object delegating to the delegate
   * @return an {@link AttributeModel} representing the field containing the
   *         delegate
   */
  private AttributeModel getDelegateAttribute(AbstractDelegatableObject delegateObject)
  {
    AttributeModel attribute = new AttributeModelImpl();
    attribute.setComment("delegate to handle operations.");
    attribute.setName(getNameManager().getLowerCaseFirstLetterName(delegateObject));
    attribute.setType(delegateObject);
    attribute.setInitialValue("new " + delegateObject.getQualifiedName() + "(this)");
    return attribute;
  }

}
